import {
  MessageBody,
  SubscribeMessage,
  WebSocketServer,
  OnGatewayConnection,
  OnGatewayDisconnect,
} from '@nestjs/websockets';
import { WebSocketGateway } from '@nestjs/websockets';
import { ChatService } from './chat.service';
import { Server, Socket } from 'socket.io';
import { Inject } from '@nestjs/common';
import { MessageService } from 'src/message/message.service';
import { UserService } from 'src/user/user.service';
import { ChatroomService } from 'src/chatroom/chatroom.service';

interface JoinRoomPayload {
  roomId: number;
  userId: number;
}

interface SendMessagePayload {
  sendUserId: number;
  roomId: number;
  message: {
    type: 'Text' | 'Image' | 'Video' | 'Audio' | 'File';
    content: string;
  };
}

interface CallPayload {
  roomId: number;
  callerId: number;
  calleeIds: number[];
  isVideoCall: boolean;
}

interface AcceptCallPayload {
  roomId: number;
  calleeId: number;
  callerId: number;
}

interface HangUpPayload {
  roomId: number;
  hangUpId: number;
  callerId: number;
  calleeIds: number[];
}

interface OfferPayload {
  fromUserId: number;
  toUserId: number;
  offer: RTCSessionDescription;
}

interface AnswerPayload {
  fromUserId: number;
  toUserId: number;
  answer: RTCSessionDescription;
}

interface IceCandidatePayload {
  fromUserId: number;
  toUserId: number;
  candidate: RTCIceCandidate;
}

@WebSocketGateway({ cors: { origin: '*' } })
export class ChatGateway implements OnGatewayConnection {
  constructor(private readonly chatService: ChatService) {}

  @WebSocketServer()
  server: Server;

  @Inject(MessageService)
  private messageService: MessageService;

  @Inject(UserService)
  private userService: UserService;

  @Inject(ChatroomService)
  private chatroomService: ChatroomService;

  // 处理用户连接，加入专属房间
  handleConnection(client: Socket) {
    const userId = client.handshake.query.userId;
    if (userId) {
      client.join(`user_${userId}`); // 用户加入专属房间
    }
  }

  // 监听 joinRoom 事件
  @SubscribeMessage('joinRoom')
  joinRoom(client: Socket, payload: JoinRoomPayload) {
    const roomName = payload.roomId.toString();

    client.join(roomName);

    this.server.to(roomName).emit('message', {
      type: 'joinRoom',
      userId: payload.userId,
    });
  }

  // 监听 sendMessage 事件
  @SubscribeMessage('sendMessage')
  async sendMessage(@MessageBody() payload: SendMessagePayload) {
    const roomName = payload.roomId.toString();

    // 1. 存储消息并获取发送者信息
    const [message, sender] = await Promise.all([
      this.messageService.add(payload.roomId, {
        senderId: payload.sendUserId,
        roomId: payload.roomId,
        type: payload.message.type,
        content: payload.message.content,
      }),
      this.userService.getUserInfoById(payload.sendUserId),
    ]);

    // 2. 通知该房间在线用户
    this.server.to(roomName).emit('message', {
      type: 'sendMessage',
      userId: payload.sendUserId,
      message: {
        ...message,
        sender,
      },
    });

    // 4. 房间所有成员 - 房间在线用户 = 未读用户（在线 + 离线）
    const users = await this.chatroomService.members(payload.roomId); // 房间所有成员
    const onlineRoomUserSockets =
      this.server.sockets.adapter.rooms.get(roomName) || new Set(); // 房间在线用户

    // 得到未读用户
    const unreadUsers = users.filter((user) => {
      const userSocketRoom = this.server.sockets.adapter.rooms.get(
        `user_${user.id}`,
      );
      return (
        !userSocketRoom || !onlineRoomUserSockets.has([...userSocketRoom][0]) // set 转数组，取第一个 socketId
      );
    });

    // 5. 在未读用户中，如果找到对应的 socket，则说明用户在线，则通知用户更新未读消息
    unreadUsers.forEach(async (user) => {
      await this.chatroomService.addUnread(user.id, payload.roomId, message.id);

      const userSocketRoom = this.server.sockets.adapter.rooms.get(
        `user_${user.id}`,
      );
      this.server.to(`user_${user.id}`).emit('message', {
        type: 'updateUnreadCount',
        roomId: payload.roomId,
        userId: payload.sendUserId,
      });
    });
  }

  // 呼叫事件
  @SubscribeMessage('call')
  async call(client: Socket, payload: CallPayload) {
    const roomName = payload.roomId.toString() + 'call';
    // 1. 首先清空房间
    this.server.to(roomName).socketsLeave(roomName);

    // 2. 自身加入房间
    client.join(roomName);

    // 3. 通知所有被呼叫者
    if (payload.calleeIds.length) {
      payload.calleeIds.forEach((calleeId) => {
        this.server.to(`user_${calleeId}`).emit('message', {
          type: 'call',
          roomId: payload.roomId,
          callerId: payload.callerId,
          calleeIds: payload.calleeIds.filter((id) => id !== calleeId),
          isVideoCall: payload.isVideoCall,
        });
      });
    }
  }

  // 监听接听事件
  @SubscribeMessage('acceptCall')
  async AcceptCall(client: Socket, payload: AcceptCallPayload) {
    const roomName = payload.roomId.toString() + 'call';

    // 1. 自身加入房间
    client.join(roomName);

    // 2. 通知所有处于通话列表中的用户
    client.to(roomName).emit('message', {
      type: 'acceptCall',
      roomId: payload.roomId,
      calleeId: payload.calleeId,
    });
  }

  // 监听 hangup 事件：calleeId表示需要挂断的ID，一般指自己
  @SubscribeMessage('hangUpCall')
  async hangup(client: Socket, payload: HangUpPayload) {
    const { roomId, hangUpId, calleeIds, callerId } = payload;
    const roomName = roomId.toString() + 'call';

    console.log('需要通知的成员', callerId, calleeIds);
    this.server.to(`user_${callerId}`).emit('message', {
      type: 'hangUpCall',
      roomId: roomId,
      hangUpId: hangUpId,
    });

    calleeIds.forEach((calleeId) => {
      this.server.to(`user_${calleeId}`).emit('message', {
        type: 'hangUpCall',
        roomId: roomId,
        hangUpId: hangUpId,
      });
    });

    // 退出房间
    client.leave(roomName);
  }

  // 监听 offer 事件 —— 这些都是点对点的，所以不需要房间
  @SubscribeMessage('offer')
  async offer(client: Socket, payload: OfferPayload) {
    const { fromUserId, toUserId, offer } = payload;
    this.server.to(`user_${toUserId}`).emit('message', {
      type: 'offer',
      fromUserId,
      offer,
    });
  }

  // 监听 answer 事件 —— 这些都是点对点的，所以不需要房间
  @SubscribeMessage('answer')
  async answer(client: Socket, payload: AnswerPayload) {
    const { fromUserId, toUserId, answer } = payload;
    this.server.to(`user_${toUserId}`).emit('message', {
      type: 'answer',
      fromUserId,
      answer,
    });
  }

  // 监听 candidate 事件 —— 这些都是点对点的，所以不需要房间
  @SubscribeMessage('candidate')
  async candidate(client: Socket, payload: IceCandidatePayload) {
    const { fromUserId, toUserId, candidate } = payload;
    this.server.to(`user_${toUserId}`).emit('message', {
      type: 'candidate',
      fromUserId,
      candidate,
    });
  }
}
